﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Reflection;
using Common.Logging;
using SpCop.Core;
using SpCop.Core.Model;
using SpCop.Model;

namespace SpCop
{
    internal class Program
    {
        private static readonly ILog Log = LogManager.GetLogger(typeof(Program));
        private static readonly IList<RuleSet> RuleSets = new List<RuleSet>(16);
        private static readonly IList<Package> Packages = new List<Package>(16);
        private static Arguments _arguments;
        private static Version _version;
        private static Report _report;

        static Program()
        {

        }



        private static void Main(string[] args)
        {
            _version = typeof(Program).Assembly.GetName().Version;
            Log.Info(m => m("SPCop Version {0}", _version.ToString()));

            _arguments = Arguments.Parse(args);

            CheckArguments();


            using (new MethodTimer("Loading Rules"))
                LoadRules();

            using (new MethodTimer("Building Package Graphs"))
                BuildGraph();

            using (new MethodTimer("Analysing Packages"))
                Analyse();

            using (new MethodTimer("Report Generation"))
                Report();
        }

        private static void CheckArguments()
        {
            if (!_arguments.ContainsArgument("file"))
            {
                Log.Warn(m => m("You should specify the /file argument to indicate the packages."));
                Log.Warn(m => m("\te.g.\t: /file=*.wsp"));
                Log.Warn(m => m("\t\t: /file=c:\\test\\*.wsp"));
                Log.Warn(m => m("\t\t: /file=file1.wsp;file2.wsp"));
                Log.Warn(m => m(""));
            }

            if (!_arguments.ContainsArgument("rules"))
            {
                Log.Warn(m => m("You should specify the /rules argument to indicate the rule files."));
                Log.Warn(m => m("\te.g.\t: /rules=*.dll"));
                Log.Warn(m => m("\t\t: /rules=c:\\test\\*.dll"));
                Log.Warn(m => m("\t\t: /rules=file1.dll;file2.dll"));
                Log.Warn(m => m(""));
            }

            if (!_arguments.ContainsArgument("console") && !_arguments.ContainsArgument("xml") &&
                !_arguments.ContainsArgument("html"))
            {
                Log.Warn(m => m("You should specify one of the following outputs : /xml /html /console"));
                Log.Warn(m => m(""));
            }
        }

        private static void Report()
        {
            string outXml = Path.GetFullPath("./output.xml");
            ReportWriter.WriteToXmlFile(_report, outXml);
            ReportWriter.ConvertToHtmlFile(outXml, Path.ChangeExtension(outXml, ".html"));
        }

        private static void Analyse()
        {
            var ruleSetDictionaries = RuleSets.ToDictionary(a => a, a => a.Rules.ToDictionary(b => b, b => a.Assembly.GetType(b.TypeName)));

            foreach (var ruleSetDictionary in ruleSetDictionaries.SelectMany(a => a.Value).Where(a => a.Value == null))
            {
                var dictionary = ruleSetDictionary;
                Log.Warn(m => m("Missing  type {1} for rule {0}", dictionary.Key.Name, dictionary.Key.TypeName));
            }

            var nullRule = new NullRule();
            var ruleInstances = ruleSetDictionaries
                .SelectMany(a => a.Value)
                .ToDictionary(a => a.Key, a => CreateRuleFromType(a, nullRule));

            var checks = (from ruleSetDictionary in ruleSetDictionaries
                                                                      from ruleTypePair in ruleSetDictionary.Value
                                                                      where

                                                                          ruleInstances.ContainsKey(ruleTypePair.Key)
                                                                      select ruleTypePair);

            Debug.Assert(ruleSetDictionaries.SelectMany(a => a.Value).Count() == checks.Count());
            var listOfProblemCollections = new List<ProblemCollection>();

            foreach (Package package in Packages)
            {
                Package pkg = package;


                Log.Debug(m => m("Analysing Package {0} : size : {1} kb", pkg.Name, pkg.FileSize / 1024));
                var pairs = checks.Where(a => a.Key.TargetVersions.IsFlagSet(pkg.ProductVersion)).ToArray();
                using (new MethodTimer(string.Format("Package Analysis for {0} to {1} rules", package.Name, pairs.Length)))
                {

                    foreach(var pair in pairs)
                        DoChecks(pkg, pair.Key, ruleInstances[pair.Key], listOfProblemCollections);
                    
                }

                Log.Debug(m => m("Package Analysed."));
            }

            var report = new Report
                             {
                                 Version = _version.ToString(),
                                 Packages = Packages.ToArray(),
                                 Problems = listOfProblemCollections.ToArray()
                             };

            _report = report;
        }

        private static RuleBase CreateRuleFromType(KeyValuePair<RuleDefinition, Type> a, NullRule nullRule)
        {
            RuleBase instance = a.Value != null
                                    ? Activator.CreateInstance(a.Value) as RuleBase
                                    : nullRule;

            if (a.Value != null && instance != null)
            {
                instance.Rule = a.Key;
            }

            return instance;
        }

        private static void DoChecks(Package package, RuleDefinition rule, RuleBase iRule,
                                     ICollection<ProblemCollection> listOfProblemCollections)
        {
            //using (
            //    new MethodTimer(string.Format("=--> {0} : Rule : {1} - {2}", package.Name, iRule.Rule.CheckId, iRule.Rule.Name))
            //    )
            //{
            var date = DateTime.Now;
            IList<Problem> listOfProblems = iRule.Check(package);
            if (iRule.Rule.CheckId == "WSP0031")
            {
               
                
            }
            if (listOfProblems.Count <= 0) return;

            var problemsCollection = new ProblemCollection { Package = package.Name, Problems = listOfProblems.ToArray(), Rule = rule };
            listOfProblemCollections.Add(problemsCollection);

            


            //}
        }

        private static void BuildGraph()
        {
            Log.Debug(m => m("Building Package Graphs."));

            string file = _arguments.GetArgumentValue("file");

            if (file.Contains("*"))
            {
                string dir = file.Substring(0, file.IndexOf('*'));
                string[] files = Directory.GetFiles(dir, file.Substring(dir.Length));
                foreach (string fileName in files)
                {
                    BuildPackageGraph(fileName);
                }
            }
            else
            {
                BuildPackageGraph(file);
            }

            Log.Info(m => m("Package Graphs Finished {0}.", Packages.Count));
        }

        private static void BuildPackageGraph(string file)
        {
            string tempLocation = Path.GetTempPath();

            string fileNameWithoutExtension = Path.GetFileNameWithoutExtension(file);

            if (fileNameWithoutExtension == null)
                throw new InvalidOperationException();

            string _extractFolder = Path.Combine(tempLocation, fileNameWithoutExtension);


            Log.Debug(m => m("Expanding Package {0} to {1}.", fileNameWithoutExtension, _extractFolder));


            using (new ExtractedPackage(file, _extractFolder))
            {
                Package packageGraph = PackageReader.ReadPackageFromFolder(fileNameWithoutExtension, _extractFolder);
                Packages.Add(packageGraph);
            }
        }

        private static void LoadRules()
        {
           
            IEnumerable<string> libraryFiles = Directory.GetFiles(".", "*.dll").Concat(new[] { "spcop.exe" });

            Log.Debug(m => m("Found {0} files to load rules from.", libraryFiles.Count()));

            foreach (string libraryFile in libraryFiles.Select(Path.GetFullPath))
            {
                Assembly assembly;
                try
                {
                   
                    assembly = Assembly.LoadFile(libraryFile);
                }
                catch (Exception)
                {
                    continue;
                }
                


                string[] resources =
                    assembly.GetManifestResourceNames().Where(a => a.ToUpperInvariant().EndsWith("RULES.XML")).
                        ToArray();
                Log.Debug(
                    m => m("Found {0} resources in assembly : {1}.", resources.Length, assembly.GetName().Name));

                foreach (var resource in resources)
                {
                    RuleSets.Add(RulesReader.ReadRulesFromAssembly(assembly, resource));
                }

               
            }

            var allRules = RuleSets.SelectMany(a => a.Rules);

            VerifyRules(allRules);

            Log.Info(
                m =>
                m("Loaded {0} Rule Sets, all containing \r\n\t {1} rules for 2007 \r\n\t {2} rules for 2010 \r\n\t {3} rules where unkown.", RuleSets.Count,
                  allRules.Count(a => a.TargetVersions.IsFlagSet(ProductVersion.SharePoint2007)),
                  allRules.Count(a => a.TargetVersions.IsFlagSet(ProductVersion.SharePoint2010)),
                  allRules.Count(a => !a.TargetVersions.IsFlagSet(ProductVersion.SharePoint2010) && !a.TargetVersions.IsFlagSet(ProductVersion.SharePoint2007))));


        }

        [Conditional("DEBUG")]
        private static void VerifyRules(IEnumerable<RuleDefinition> rules)
        {
            var count = rules.Select(a => a.CheckId).Distinct().Count();
            var i = rules.Count();
            var hasDuplicateNames = count != i;
            Debug.Assert(!hasDuplicateNames);
        }
    }

    public static class ProductVersionHelper
    {
        public static bool IsFlagSet(this ProductVersion version, ProductVersion targetVersion)
        {

            return (version & targetVersion) == targetVersion;
        }

    }
}